/**
 * Fichier Db4oDaoGenerique.java : Définition de classe
 * fr.unice.gnpt.core.dao.db4o.Db4oDaoGenerique : Fabrique de DAO de type
 * Générique pour DB4O
 * 
 * Auteurs : Groupe GNPT : Gastinel, Nicolas, Pellegrino, Trovato
 */
package fr.unice.gnpt.core.dao.db4o;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.ext.ExtObjectContainer;

import fr.unice.gnpt.core.dao.DaoException;
import fr.unice.gnpt.core.dao.DaoGenerique;
import fr.unice.gnpt.core.dao.connexion.Connexion;
import fr.unice.gnpt.core.dao.connexion.ConnexionDb4o;

/**
 * Classe abstraite de gestion de DAO de type générique pour DB4O.
 * <p>
 * DB4O est utilisé en mode fichier. Il est donc possible de passer le nom de
 * fichier associée à la base de données via le constructeur ou bien avec la
 * méthode {@link #setConnexion(Connexion)}. Cela permettra de créer une
 * connexion.
 * 
 * @author gnpt
 * @version 0.1.4
 * 
 * @param <T>
 *            le type d'objet associé au DAO.
 * @param <ID>
 *            le type de l'identifiant unique associé à l'objet de type
 *            <code>T</code>.
 */
public abstract class Db4oDaoGenerique<T, ID extends Serializable> implements
		DaoGenerique<T, ID> {
	/**
	 * Type de classe sur laquelle le DAO Générique travaille.
	 */
	private Class<T> classeEntite;
	/**
	 * Joue le rôle de la connexion à la base de données.
	 */
	private Connexion<ObjectContainer> connexion;

	/**
	 * Constructeur par défaut.
	 * 
	 * @param classEntite
	 *            classe sur laquelle le DAO travaille.
	 * @param nomFichierBdd
	 *            connexion à la base de donnée DB4O.
	 */
	public Db4oDaoGenerique(Class<T> classEntite, String nomFichierBdd) {
		this.classeEntite = classEntite;
		this.connexion = new ConnexionDb4o(nomFichierBdd);
	}

	/**
	 * Ajoute une nouvelle entité dans la base de données.
	 * <p>
	 * Précondition : l'entité passée en paramètre n'a pas déjà une identité
	 * dans la base.
	 * <p>
	 * Postcondition : les données contenues dans l'entité sont ajoutées dans la
	 * base.
	 * 
	 * @throws DaoException
	 *             si la connexion est fermée ou que le fichier associé à la
	 *             base de données est configurée en lecture seule.
	 */
	@Override
	public void create(T entite) throws DaoException {
		((ConnexionDb4o) this.connexion)
				.reouvrirConnexionSiPasRellementFermee();

		if (this.connexion.getConnexion().ext().isStored(entite)) { throw new DaoException(
				"L'entité a déjà une identité."); }

		try {
			this.connexion.getConnexion().store(entite);
		} catch (Exception e) {
			throw new DaoException(
					"Erreur lors de la persistance de l'entité : " + entite
							+ ".", e);
		}
	}

	/**
	 * Supprime les données correspondant à l'entité (passée en paramètre) dans
	 * la base de données.
	 * <p>
	 * Précondition : le paramètre <code>entite</code> est une entité qui a déjà
	 * une identité dans la base.
	 * <p>
	 * Postcondition : les données qui ont cette identité sont supprimées de la
	 * base.
	 * 
	 * @throws DaoException
	 *             si l'entitée passée en paramètre n'a pas d'identité dans la
	 *             base ou pour une autre raison.
	 */
	@Override
	public void delete(T entite) throws DaoException {
		((ConnexionDb4o) this.connexion)
				.reouvrirConnexionSiPasRellementFermee();

		if (!this.connexion.getConnexion().ext().isStored(entite)) { throw new DaoException(
				"Aucune entité trouvée pour : " + entite); }
		try {
			this.connexion.getConnexion().delete(entite);
		} catch (Exception e) {
			throw new DaoException(
					"Erreur lors de la supression de l'entité : " + entite
							+ ".", e);
		}
	}

	/**
	 * @see fr.unice.gnpt.core.dao.DaoGenerique#findAll()
	 */
	@Override
	public Collection<T> findAll() throws DaoException {
		((ConnexionDb4o) this.connexion)
				.reouvrirConnexionSiPasRellementFermee();

		try {
			Collection<T> res = new ArrayList<T>();
			ObjectSet<T> set = this.connexion.getConnexion().query(
					this.classeEntite);

			for (T t : set) {
				if (!res.contains(t)) {
					res.add(t);
				}
			}

			return res;
		} catch (Exception e) {
			throw new DaoException(
					"Erreur lors de la recherche de tout les objets de type "
							+ this.classeEntite.getSimpleName() + ".", e);
		}
	}

	/**
	 * Met à jour une entité se trouvant déjà dans la base de données.
	 * <p>
	 * Précondition : le paramètre <code>entite</code> est une entité qui a déjà
	 * une identité dans la base.
	 * <p>
	 * Postcondition : les donnees contenues dans l'entité sont mises à jour
	 * dans la base.
	 * 
	 * @param entite
	 *            l'entité qui a une identité dans la base et qui contient les
	 *            informations à modifier dans la base.
	 * @return une entité qui contient les données sur l'entité telles qu'elles
	 *         étaient dans la base avant la modification.
	 * @throws DaoException
	 */
	@Override
	public T update(T entite) throws DaoException {
		((ConnexionDb4o) this.connexion)
				.reouvrirConnexionSiPasRellementFermee();

		ExtObjectContainer eoc = this.connexion.getConnexion().ext();

		if (!eoc.isStored(entite)) { throw new DaoException(
				"L'entité n'a pas été trouvée."); }

		try {
			eoc.store(entite);
		} catch (Exception e) {
			throw new DaoException(
					"Erreur lors de la mise à jour de l'entité : " + entite, e);
		}

		return eoc.peekPersisted(entite, 1, true);
	}

	/**
	 * Accesseur pour la connexion associée au DAO.
	 * 
	 * @return la connexion associée au DAO.
	 */
	public Connexion<ObjectContainer> getConnexion() {
		return this.connexion;
	}

	/**
	 * Mutateur pour la connexion associée au DAO. Ne fait rien si le DAO à déjà
	 * une connexion en cours ouverte.
	 * 
	 * @param connexion
	 *            la nouvelle connexion associée au DAO.
	 */
	@SuppressWarnings("unchecked")
	public void setConnexion(Connexion connexion) {
		if (this.connexion.getConnexion().ext().isClosed()) {
			this.connexion = connexion;
		}
	}

	/**
	 * Accesseur générique pour faire un findAll sur une autre classe que
	 * l'entité courante.
	 * 
	 * @param klass
	 *            la classe du type a récupérer.
	 * @param <E>
	 *            le type sur lequel on fait la requête.
	 * @return la collection de tous les éléments de ce type.
	 * @throws DaoException
	 *             en cas d'erreur lors de l'accès aux données.
	 */
	protected <E> Collection<E> findAll(Class<E> klass)
			throws DaoException {
		((ConnexionDb4o) this.connexion)
				.reouvrirConnexionSiPasRellementFermee();

		try {
			Collection<E> res = new ArrayList<E>();
			ObjectSet<E> set = this.connexion.getConnexion().query(klass);

			for (E t : set) {
				if (!res.contains(t)) {
					res.add(t);
				}
			}

			return res;
		} catch (Exception e) {
			throw new DaoException(
					"Erreur lors de la recherche de tout les objets de type "
							+ klass.getSimpleName() + ".", e);
		}
	}
}
